package cn.bugstack.mybatis.bindings;

import cn.bugstack.mybatis.mapping.MappedStatement;
import cn.bugstack.mybatis.mapping.SqlCommandType;
import cn.bugstack.mybatis.reflection.ParamNameResolver;
import cn.bugstack.mybatis.session.Configuration;
import cn.bugstack.mybatis.session.SqlSession;

import java.lang.reflect.Method;
import java.util.*;

/**
 * 包装mapper接口中的某个方法
 */
public class MapperMethod {

    private final SqlCommand command;
    private final MethodSignature method;



    public MapperMethod(Configuration configuration, Class<?> mapperInterface, Method method) {
        this.command = new SqlCommand(configuration, mapperInterface, method);
        this.method = new MethodSignature(configuration, method);

    }


    /**
     * 执行mapper接口的方法，就是调用sqlSession的相应方法
     * @param sqlSession
     * @param args
     * @return
     */
    public Object execute(SqlSession sqlSession, Object[] args) throws Exception {
        Object param = method.convertArgsToSqlCommandParam(args);

        Object result = null;
        switch (command.type) {
            case DELETE:
                result = sqlSession.delete(command.name, param);
                break;
            case INSERT:
                result = sqlSession.insert(command.name, param);
                break;
            case SELECT:
                if (method.returnsMany) {
                    result = sqlSession.selectList(command.name, param);
                } else {
                    result =  sqlSession.selectOne(command.name, param);
                }
                break;
            case UPDATE:
                result = sqlSession.update(command.name, param);
                break;
            default:
                throw new RuntimeException("Unknown mapper method for : "+ command.name);
        }
        return result;
    }

    /**
     * 将mapper接口的方法和sql指令类型绑定
     */
    public static class SqlCommand {

        private final String name;
        private final SqlCommandType type;

        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
            String statementName = mapperInterface.getName() + "." + method.getName();
            MappedStatement ms = configuration.getMappedStatement(statementName);
            name = ms.getId();
            type = ms.getSqlCommandType();
        }

        public String getName() {
            return name;
        }

        public SqlCommandType getType() {
            return type;
        }
    }

    /**
     * 方法签名
     */
    public static class MethodSignature {

        private final boolean returnsMany;

        private final SortedMap<Integer, String> params;
        private final Class<?> returnType;
        private final ParamNameResolver paramNameResolver;

        public MethodSignature(Configuration configuration, Method method) {
            this.returnType = method.getReturnType();
            this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());

            this.params = Collections.unmodifiableSortedMap(getParams(method));
            this.paramNameResolver = new ParamNameResolver(configuration, method);

        }

        public Object convertArgsToSqlCommandParam(Object[] args) {
            return paramNameResolver.getNamedParams(args);
        }

        private SortedMap<Integer, String> getParams(Method method) {
            // 用一个TreeMap，这样就保证还是按参数的先后顺序
            final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
            final Class<?>[] argTypes = method.getParameterTypes();
            // Integer id,String name
            for (int i = 0; i < argTypes.length; i++) {
                String paramName = String.valueOf(params.size());
                // 不做 Param 的实现，这部分不处理。如果扩展学习，需要添加 Param 注解并做扩展实现。
                // 0-》id  1-》name
                params.put(i, paramName);
            }
            return params;
        }

    }

    /**
     * 参数map，静态内部类,更严格的get方法，如果没有相应的key，报错
     */
    public static class ParamMap<V> extends HashMap<String, V> {

        private static final long serialVersionUID = -2212268410512043556L;

        @Override
        public V get(Object key) {
            if (!super.containsKey(key)) {
                throw new RuntimeException("Parameter '" + key + "' not found. Available parameters are " + keySet());
            }
            return super.get(key);
        }

    }
}
